package com.lazy.aspect;

import com.lazy.annotation.Idempotent;
import com.lazy.exception.CustomRedisException;
import com.lazy.expression.ExpressionKeyResolver;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.ObjectUtils;

import java.util.Optional;

/**
 * @author fengshaoyu
 * @date 2024/1/30 11:13
 */
@Aspect
public record IdempotentAspect(StringRedisTemplate stringRedisTemplate, ExpressionKeyResolver expressionKeyResolver) {

    @Pointcut("@annotation(com.lazy.annotation.Idempotent)")
    public void pointCut() {
    }


    @Around("pointCut()")
    public Object around(ProceedingJoinPoint jp) throws Throwable {
        Object proceed;
        String resolver = null;
        MethodSignature signature;
        Idempotent annotation = null;
        String baseKey = "idempotent:";
        try {
            signature = (MethodSignature) jp.getSignature();
            annotation = AnnotationUtils.findAnnotation(signature.getMethod(), Idempotent.class);
            if (!ObjectUtils.isEmpty(annotation)) {
                resolver = expressionKeyResolver.resolver(annotation, jp);
                Optional<Boolean> exist = Optional.ofNullable(stringRedisTemplate.hasKey(baseKey + resolver));
                if (exist.isPresent() && Boolean.TRUE.equals(exist.get())) {
                    throw new CustomRedisException(annotation.info());
                }
                stringRedisTemplate.opsForValue().set(baseKey + resolver, "", annotation.expireTime(), annotation.timeUnit());
            }
            proceed = jp.proceed();
        } finally {
            if (!ObjectUtils.isEmpty(annotation) && annotation.delKey()) {
                stringRedisTemplate.delete(baseKey + resolver);
            }
        }
        return proceed;
    }
}
